/** @file FsStorage.h
	@brief Definition of FsStorage_t.
*/

#ifndef __fsstorage_h__
#define __fsstorage_h__

#include <cerrno>
#include <cstddef>
#include <cstring>
#include <sys/stat.h>

#include "base64.h"
#include "exception.h"
#include "Storage.h"
#include "util.h"

/// Minimum access needed for storage folder.
#ifdef _WIN32
#define MIN_STORAGE_ACCESS 04
#else
#define MIN_STORAGE_ACCESS 05
#endif //_WIN32

/************************************************
 * @brief class FsStorage_t.
 * 
 * Storage based on filesystem: each record is the file.
 ************************************************/
class FsStorage_t : public Storage_t {
public:
		/// Max key length.
		enum{MAX_KEY_LENGTH = 1024};

private:
		/// Buffer with path to storage folder.
		char * path;

		/// Length of path part in buffer.
		size_t path_length;
		
		/// Type for transformed key.
		typedef char BKey_t[ base64_length(MAX_KEY_LENGTH) + 1 ];

		/// Check key.
		void check_key(const char * _key) const throw(std::invalid_argument) {
				const size_t k_l = strlen(_key);
				
				if( 0 == k_l || MAX_KEY_LENGTH < k_l )
						throw std::invalid_argument("Fs2Storage_t::check_key(): invalid key.");
		}

		/// Transform key to base64 encoding.
		void transform_key(BKey_t & _bkey, const char * _key) const throw() {		
				const size_t bk_l = base64_encode(_key, strlen(_key), _bkey, base64_length(MAX_KEY_LENGTH));

				// there are need some changes in b64 encoding: each '/' -> '*'
				for(size_t i = 0; i < bk_l; ++i)
						if( '/' == _bkey[i] )
								_bkey[i] = '*';

				_bkey[ bk_l ] = '\0';
		}

public:
		/// @brief Constructor.
		/// Check is there exist storage folder with proper access rule?
		/// @exception std::bad_alloc return by new
		/// @exception std::io_error InOut error occured
		/// @exception std::invalid_argument there is wrong _path to storage
		FsStorage_t(const char * _path) throw(std::bad_alloc, std::io_error, std::invalid_argument) {
				struct stat status;
				if( -1 == util_stat(_path, &status) ) 
						if( EACCES != errno && ENOENT != errno )
								throw std::io_error("FsStorage_t::FsStorage_t(): function stat() return -1.");
						else
								throw std::invalid_argument("FsStorage_t::FsStorage_t(): there is no storage at this path.");
				
				if( ! util_isdir(status.st_mode) ) 
						throw std::invalid_argument("FsStorage_t::FsStorage_t(): path is not point to dirrectory.");
				
				if( -1 == util_access(_path, MIN_STORAGE_ACCESS) ) 
						if( EACCES != errno )
								throw std::io_error("FsStorage_t::FsStorage_t(): function access() return -1.");
						else
								throw std::invalid_argument("FsStorage_t::FsStorage_t(): program has not access to path.");
				
				path_length = strlen(_path);
				path = new char[ path_length + decode_length(FsStorage_t::MAX_KEY_LENGTH) + 2 ];
				
				memcpy(path, _path, path_length); 
				
				if( '/' != path[ path_length - 1 ] )
						path[ path_length++ ] = '/';
		}

		/// @brief Destructor.		
		~FsStorage_t() throw() {
				delete [] path;
		}

		/// @brief Set object state.
		///
		/// @exception std::io_error InOut error occured
		/// @exception std::bad_alloc return by _obj->get_state
		/// @exception std::invalid_argument return by _obj->get_state
		void set(const char * _key, const StorageObject_t * _obj) throw(std::bad_alloc,  std::io_error, std::invalid_argument) {
				BKey_t bkey;

				check_key(_key);
				transform_key(bkey, _key);

				char * state;
				size_t size;

				_obj->get_state(state, size);
				
				strcpy(path + path_length, bkey);
				int fd;
				if( -1 == (fd = util_open(path, util_wronly | util_trunc | util_creat)) ) {
						delete [] state;
						throw std::io_error("FsStorage_t::set(): function open() return -1.");
				}
				
				if( -1 == util_writen(fd, state, size) ) {
						util_close(fd);
						delete [] state;
						throw std::io_error("FsStorage_t::set(): function write() return -1.");
				}
				
				delete [] state;
				
				if( -1 == util_close(fd) )
						throw std::io_error("FsStorage_t::set(): function close() return -1.");
		}

		/// @brief Get object state.
		///
		/// @exception std::io_error InOut error occured
		/// @exception std::bad_alloc return by _obj->set_state
		/// @exception std::invalid_argument return by _obj->set_state
		void get(const char * _key, StorageObject_t * _obj) throw(std::bad_alloc, std::io_error, std::invalid_argument) {
				BKey_t bkey;

				check_key(_key);
				transform_key(bkey, _key);
				
				strcpy(path + path_length, bkey);
				int fd = util_open(path, util_rdonly);
				if( -1 == fd )
						if( ENOENT == errno ) {
								_obj->set_state(NULL, 0);
								return;
						} else
								throw std::io_error("FsStorage_t::get(): function open() return -1.");
				
				struct stat status;
				if( -1 == util_fstat(fd, &status) ) {
						util_close(fd);
						throw std::io_error("FSStorage_t::get(): function fstat() return -1.");
				}
				
				char * state = NULL;
				
				try {
						state = new char[ status.st_size ];
						
						if( -1 == util_readn(fd, state, status.st_size) )
								throw std::io_error("FsStorage_t::get(): function read() return -1.");

						_obj->set_state(state, status.st_size);

						delete [] state;						

				} catch(...) {
						util_close(fd);
						delete [] state;
						throw;
				}
				
				if( -1 == util_close(fd) )
						throw std::io_error("FsStorage_t::get(): function close() return -1.");
		}
		
};

#undef MIN_STORAGE_ACCESS

#endif //__fsstorage_h__
